package com.vois.jack.btmgr.util;

public class BlockRingBufffer {
    private Logger logger = Logger.getLogger(BlockRingBufffer.class);
    private byte[] buffer;
    private int head;
    private int tail;
    private final Object lock = new Object();
    private int ringBufferSize;
    private boolean isReseting;

    public BlockRingBufffer(int ringBufferSize) {
        this.ringBufferSize = ringBufferSize;
        buffer = new byte[ringBufferSize];
        head = tail = 0;
    }

    public boolean addBuffer(byte[] buffer, int bufferSize) throws InterruptedException {
        return addBuffer(buffer, 0, bufferSize);
    }

    public boolean addBuffer(byte[] buffer, int bufOffset, int bufferSize) throws InterruptedException{
        int i;
        if (bufferSize > ringBufferSize - 1) {
            return false;
        }

        if (isEmpty()) {
            for (i = 0; i < bufferSize; i++) {
                this.buffer[tail] = buffer[bufOffset + i];
                tail = (tail + 1) % ringBufferSize;
            }

            synchronized (lock) {
                logger.d( "addBuffer: ready to notify all that wait for get buffer");
                lock.notifyAll();
            }
        }else {
            int addBufSize;
            int offset = 0;
            while (offset < bufferSize) {
                addBufSize = getDataSize();
                if (addBufSize >= (bufferSize - offset)) {
                    addBufSize = (bufferSize - offset);
                }
                for (i = 0; i < addBufSize; i++) {
                    this.buffer[tail] = buffer[bufOffset + offset + i];
                    tail = (tail + 1) % ringBufferSize;
                }
                offset += addBufSize;

                if (isFull()) {
                    synchronized (lock) {
                        logger.d( "addBuffer: wait for empty.....");
                        lock.wait();
                    }

                    if (isReseting) {
                        logger.d( "addBuffer: clear is executed, should return");
                        isReseting = false;
                        return false;
                    }
                }
            }
        }

        return true;
    }

    public int getBuffer(byte[] buffer, int bufferSize) throws InterruptedException {
        return getBuffer(buffer, 0, bufferSize);
    }

    public int getBuffer(byte[] buffer, int bufferOffset, int bufferSize) throws InterruptedException{
        boolean isFull = isFull();

        if (isEmpty()) {
            synchronized (lock) {
                logger.d( "getBuffer: buffer is empty, wait for data");
                lock.wait();
            }

            if (isReseting) {
                isReseting = false;
                logger.d( "getBuffer: clear is executed, should return 0");
                return 0;
            }
        }

        int i;
        int getBufferSize = getDataSize();
        if (getBufferSize >= bufferSize) {
            getBufferSize = bufferSize;
        }

        for (i = 0; i < getBufferSize; i++) {
            buffer[bufferOffset + i] = this.buffer[head];
            head = (head + 1) % ringBufferSize;
        }

        if (isFull) {
            synchronized (lock) {
                logger.d( "getBuffer: notify all that write buffer into it");
                lock.notifyAll();
            }
        }

        return getBufferSize;
    }

    public boolean isFull() {
        return (head == ((tail + 1) % ringBufferSize));
    }

    public boolean isEmpty() {
        return  (head == tail);
    }

    public int getFreeSize() {
        return (head - 1 - tail + ringBufferSize) % ringBufferSize;
    }

    public int getDataSize() {
        return (tail - head + ringBufferSize) % ringBufferSize;
    }

    public void clear() {
        head = 0;
        tail = 0;
    }

    public void reset() {
        isReseting = true;
        synchronized (lock) {
            logger.d( "clear: should notify all that waiting.....");
            lock.notifyAll();
        }
    }

    public void destroy() {
        reset();
        buffer = null;
    }
}
